package fr.umlv.m2.jee.pmlv.service.user.impl;

import fr.umlv.m2.jee.pmlv.persistence.ldapLink.UserSpecialty;
import fr.umlv.m2.jee.pmlv.persistence.ldapLink.UserSubject;
import fr.umlv.m2.jee.pmlv.persistence.ldapLink.dao.HibernateUserSpecialtyDAO;
import fr.umlv.m2.jee.pmlv.persistence.ldapLink.dao.HibernateUserSubjectDao;
import java.util.List;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import fr.umlv.m2.jee.pmlv.service.user.IUserService;
import fr.umlv.m2.jee.pmlv.service.user.User;
import fr.umlv.m2.jee.pmlv.service.user.UserAttributeMapper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import org.springframework.ldap.filter.OrFilter;
import org.springframework.transaction.annotation.Transactional;

/**
 * Class that permits interaction with the LDAP.
 * 
 * @author Karim HAROUNI
 * 
 */
public class UserService implements IUserService {

	/**
	 * search node for users in the LDAP tree
	 */
	private static final String USERS_OU = "ou=users";
	/**
	 * search node for groups in the LDAP tree
	 */
	private static final String GROUPS_OU = "ou=groups";
	/**
	 * object to interact with the LDAP
	 */
        @Autowired
        @Qualifier("ldapTemplate")
	private LdapTemplate ldapTemplate;
	/**
	 * Ldap context
	 */
        @Autowired
        @Qualifier("contextSource")
	private LdapContextSource contextSource;
        /*
         * UserSpecialtyDao
         */
        @Autowired
        @Qualifier("hibernateUserSpecialtyDAO")
        private HibernateUserSpecialtyDAO uspe_dao;
        /*
         * UserSubjectDao
         */
        @Autowired
        @Qualifier("hibernateUserSubjectDAO")
        private HibernateUserSubjectDao usub_dao;


        //Setter
	/**
	 * setter of the interaction object
	 * 
	 * @param ldapTemplate
	 */
	public void setLdapTemplate(LdapTemplate ldapTemplate) {
		this.ldapTemplate = ldapTemplate;
	}

	/**
	 * setter of the LDAP context
	 * 
	 * @param contextSource
	 */
	public void setContextSource(LdapContextSource contextSource) {
		this.contextSource = contextSource;
	}


        //Service
        @SuppressWarnings("unchecked")
	@Override
	public List<User> getAllUsers() throws NamingException {
		AndFilter andFilter = new AndFilter();
		andFilter.and(new EqualsFilter("objectclass", "person"));
		List<User> results = results = ldapTemplate.search(USERS_OU, andFilter.encode(),new UserAttributeMapper());
		for(User user:results)
                {
                    try {
			user.setGroup((getUserGroups(user.getCommonName())).replace("cn=", ""));
                    } catch (NamingException e) {
                            throw (e);
                    }
                }
		return results;
	}

	@SuppressWarnings("unchecked")
	@Override
	public User getUserDetails(String uId, String password) throws NamingException {
		// setup search criteria
		AndFilter andFilter = new AndFilter();
		andFilter.and(new EqualsFilter("objectclass", "person"));
		andFilter.and(new EqualsFilter("uId", uId));
		andFilter.and(new EqualsFilter("userPassword", password));
		// search for the user in the uid and password of the user in the LDAP
		// and returns list of users
		List<User> results = null;
		results = ldapTemplate.search(USERS_OU, andFilter.encode(),
				new UserAttributeMapper());
		// if no user found return null
		if (results.isEmpty())
			return null;
		// return the first user (uId is unique for users so we always have a
		// maximum of one user)
		User user = results.get(0);
		try {
			user.setGroup((getUserGroups(user.getCommonName())).replace("cn=", ""));
		} catch (NamingException e) {
			throw (e);
		}
		return user;
	}

	/**
	 * method to get group of a user
	 */
	@Override
	public String getUserGroups(String commonName) throws NamingException {
		// set up the DN of the user
		String userDN = new StringBuffer().append("cn=").append(commonName).append(",ou=users,o=pmlv").toString();
		// set up search criteria
		AndFilter filter = new AndFilter();
		filter.and(new EqualsFilter("objectclass", "groupOfNames"));
		filter.and(new EqualsFilter("member", userDN));
		// Set up search constraints
		SearchControls cons = new SearchControls();
		cons.setSearchScope(SearchControls.ONELEVEL_SCOPE);
		// set up the DirContext in witch we search

		DirContext context = contextSource.getReadOnlyContext();
		@SuppressWarnings("rawtypes")
		NamingEnumeration results;
		try {
			// search for the groups
			results = context.search(GROUPS_OU, filter.encode(), cons);
		} catch (NamingException e) {
			// if there is a naming exception throw it
			throw (e);
		}
		// return the first group name of results (a user can only have one
		// group at a time)
		if (!results.hasMore())
			return null;
		return ((SearchResult) results.next()).getName();
	}

        /**
        * method to get student who follow this subject
        */
        @Override
        public List<User> getStudentBySubjectIdAndByYear(Long id, int year) throws NamingException {
            AndFilter andFilter = new AndFilter();
            andFilter.and(new EqualsFilter("objectclass", "person"));

            List<UserSpecialty> users = uspe_dao.findBySubjectIdAndByYear(id, year);
            if(users.isEmpty())
                return new ArrayList<User>();
            OrFilter orFilter = new OrFilter();
            for(UserSpecialty u:users)
            {
                orFilter.or(new EqualsFilter("uId", u.getUid()));
            }
            andFilter.and(orFilter);

            List<User> results = null;
            results = ldapTemplate.search(USERS_OU, andFilter.encode(),new UserAttributeMapper());
            for(User user:results)
            {
                try {
                    user.setGroup((getUserGroups(user.getCommonName())).replace("cn=", ""));
                } catch (NamingException e) {
                        throw (e);
                }
            }

            Collections.sort(results, new Comparator<User>() {
                @Override
                public int compare(User o1, User o2) {
                    if(o1.getLastname().equals(o2.getLastname()))
                        return o1.getFirstname().compareTo(o2.getFirstname());
                    return o1.getLastname().compareTo(o2.getLastname());
                }
            });
            return results;
        }

        /**
         * method to get years when professor teach a subject
         */
        @Override
        public List<Integer> getYearsByProfessorId(String uid) throws NamingException
        {
            List<UserSubject> usub = usub_dao.findByUserUid(uid);
            List<Integer> years = new ArrayList<Integer>();
            for(UserSubject us:usub)
            {
                if(!years.contains(new Integer((int)us.getYear())))
                    years.add(new Integer((int)us.getYear()));
            }
            Collections.sort(years, new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    if(o1.intValue()==o2.intValue()) return 0;
                    if(o1.intValue()<o2.intValue()) return 1;
                    return -1;
                }
            });
            return years;
        }

        /**
         * method to get years when student is in university
         */
        @Override
        public List<Integer> getYearsByStudentId(String uid) throws NamingException
        {
            List<UserSpecialty> usub = uspe_dao.findByUserUid(uid);
            List<Integer> years = new ArrayList<Integer>();
            for(UserSpecialty us:usub)
            {
                if(!years.contains(new Integer((int)us.getYear())))
                    years.add(new Integer((int)us.getYear()));
            }
            Collections.sort(years, new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    if(o1.intValue()==o2.intValue()) return 0;
                    if(o1.intValue()<o2.intValue()) return 1;
                    return -1;
                }
            });
            return years;
        }

    @Override
    public List<User> getResponsibleBySubjectAndByYear(long sub_id, int year) throws NamingException {
        AndFilter andFilter = new AndFilter();
        andFilter.and(new EqualsFilter("objectclass", "person"));

        List<UserSubject> users = usub_dao.findResponsibleBySubjectAndByYear(sub_id, year);
        if(users.isEmpty())
            return new ArrayList<User>();
        OrFilter orFilter = new OrFilter();
        for(UserSubject u:users)
        {
            orFilter.or(new EqualsFilter("uId", u.getUid()));
        }
        andFilter.and(orFilter);

        List<User> results = null;
        results = ldapTemplate.search(USERS_OU, andFilter.encode(),new UserAttributeMapper());
        for(User user:results)
        {
            try {
                user.setGroup((getUserGroups(user.getCommonName())).replace("cn=", ""));
            } catch (NamingException e) {
                    throw (e);
            }
        }

        Collections.sort(results, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                if(o1.getLastname().equals(o2.getLastname()))
                    return o1.getFirstname().compareTo(o2.getFirstname());
                return o1.getLastname().compareTo(o2.getLastname());
            }
        });
        return results;
    }
}
